from typing import Dict, List, Optional
import pandas as pd
import numpy as np
from scipy import stats

class PerformanceAnalyzer:
    def __init__(self, initial_capital: float):
        self.initial_capital = initial_capital

    def analyze_strategy(
        self,
        portfolio_values: List[float],
        trades: List[Dict],
        benchmark_returns: Optional[pd.Series] = None
    ) -> Dict:
        """
        Comprehensive strategy performance analysis
        """
        returns = pd.Series(portfolio_values).pct_change()
        
        # Basic metrics
        total_return = (portfolio_values[-1] - self.initial_capital) / self.initial_capital
        daily_returns = returns.mean()
        volatility = returns.std()
        
        results = {
            'basic_metrics': {
                'total_return': total_return,
                'annual_return': self._calculate_annual_return(portfolio_values),
                'volatility': volatility,
                'sharpe_ratio': self._calculate_sharpe_ratio(returns),
                'sortino_ratio': self._calculate_sortino_ratio(returns),
                'max_drawdown': self._calculate_max_drawdown(portfolio_values),
                'win_rate': self._calculate_win_rate(trades),
            },
            'risk_metrics': {
                'var_95': self._calculate_var(returns, 0.95),
                'cvar_95': self._calculate_cvar(returns, 0.95),
                'skewness': returns.skew(),
                'kurtosis': returns.kurtosis(),
            },
            'trade_metrics': self._analyze_trades(trades),
        }
        
        # Add benchmark comparison if provided
        if benchmark_returns is not None:
            results['benchmark_metrics'] = self._compare_to_benchmark(
                returns, benchmark_returns
            )
            
        return results

    def _calculate_annual_return(self, portfolio_values: List[float]) -> float:
        """
        Calculate annualized return
        """
        total_days = len(portfolio_values)
        total_return = portfolio_values[-1] / portfolio_values[0] - 1
        annual_return = (1 + total_return) ** (252 / total_days) - 1
        return annual_return

    def _calculate_sharpe_ratio(
        self,
        returns: pd.Series,
        risk_free_rate: float = 0.01
    ) -> float:
        """
        Calculate Sharpe Ratio
        """
        excess_returns = returns - risk_free_rate/252
        if len(excess_returns) < 2:
            return 0.0
        return np.sqrt(252) * excess_returns.mean() / excess_returns.std()

    def _calculate_sortino_ratio(
        self,
        returns: pd.Series,
        risk_free_rate: float = 0.01
    ) -> float:
        """
        Calculate Sortino Ratio
        """
        excess_returns = returns - risk_free_rate/252
        downside_returns = excess_returns[excess_returns < 0]
        if len(downside_returns) < 2:
            return 0.0
        downside_std = np.sqrt(np.mean(downside_returns**2))
        return np.sqrt(252) * excess_returns.mean() / downside_std

    def _calculate_max_drawdown(self, portfolio_values: List[float]) -> float:
        """
        Calculate Maximum Drawdown
        """
        peak = portfolio_values[0]
        max_drawdown = 0.0
        
        for value in portfolio_values:
            if value > peak:
                peak = value
            drawdown = (peak - value) / peak
            max_drawdown = max(max_drawdown, drawdown)
        
        return max_drawdown

    def _calculate_win_rate(self, trades: List[Dict]) -> float:
        """
        Calculate Win Rate
        """
        if not trades:
            return 0.0
            
        profitable_trades = sum(
            1 for trade in trades
            if trade.get('type') == 'sell' and
            trade.get('revenue', 0) > trade.get('cost', 0)
        )
        return profitable_trades / len(trades)

    def _calculate_var(self, returns: pd.Series, confidence_level: float) -> float:
        """
        Calculate Value at Risk
        """
        return np.percentile(returns, (1 - confidence_level) * 100)

    def _calculate_cvar(self, returns: pd.Series, confidence_level: float) -> float:
        """
        Calculate Conditional Value at Risk (Expected Shortfall)
        """
        var = self._calculate_var(returns, confidence_level)
        return returns[returns <= var].mean()

    def _analyze_trades(self, trades: List[Dict]) -> Dict:
        """
        Analyze trading patterns and statistics
        """
        if not trades:
            return {}
            
        profits = []
        holding_periods = []
        trade_sizes = []
        
        for i in range(0, len(trades), 2):
            if i + 1 < len(trades):
                buy_trade = trades[i]
                sell_trade = trades[i + 1]
                
                profit = sell_trade.get('revenue', 0) - buy_trade.get('cost', 0)
                profits.append(profit)
                
                holding_period = (sell_trade['date'] - buy_trade['date']).days
                holding_periods.append(holding_period)
                
                trade_sizes.append(buy_trade.get('cost', 0))
        
        return {
            'avg_profit': np.mean(profits) if profits else 0,
            'avg_holding_period': np.mean(holding_periods) if holding_periods else 0,
            'avg_trade_size': np.mean(trade_sizes) if trade_sizes else 0,
            'profit_factor': (
                sum(p for p in profits if p > 0) / abs(sum(p for p in profits if p < 0))
                if any(p < 0 for p in profits) else float('inf')
            ),
            'max_consecutive_losses': self._calculate_max_consecutive_losses(profits)
        }

    def _compare_to_benchmark(
        self,
        strategy_returns: pd.Series,
        benchmark_returns: pd.Series
    ) -> Dict:
        """
        Compare strategy performance to benchmark
        """
        # Align returns series
        common_index = strategy_returns.index.intersection(benchmark_returns.index)
        strategy_returns = strategy_returns[common_index]
        benchmark_returns = benchmark_returns[common_index]
        
        # Calculate beta
        covariance = np.cov(strategy_returns, benchmark_returns)[0][1]
        benchmark_variance = np.var(benchmark_returns)
        beta = covariance / benchmark_variance if benchmark_variance != 0 else 1
        
        # Calculate alpha (annualized)
        risk_free_rate = 0.01  # Assuming 1% risk-free rate
        strategy_excess_return = strategy_returns.mean() * 252 - risk_free_rate
        benchmark_excess_return = benchmark_returns.mean() * 252 - risk_free_rate
        alpha = strategy_excess_return - beta * benchmark_excess_return
        
        # Calculate information ratio
        tracking_error = (strategy_returns - benchmark_returns).std() * np.sqrt(252)
        information_ratio = (
            (strategy_returns.mean() - benchmark_returns.mean()) * 252 / tracking_error
            if tracking_error != 0 else 0
        )
        
        return {
            'alpha': alpha,
            'beta': beta,
            'correlation': strategy_returns.corr(benchmark_returns),
            'information_ratio': information_ratio,
            'tracking_error': tracking_error
        }

    def _calculate_max_consecutive_losses(self, profits: List[float]) -> int:
        """
        Calculate maximum consecutive losing trades
        """
        max_consecutive = current_consecutive = 0
        
        for profit in profits:
            if profit < 0:
                current_consecutive += 1
                max_consecutive = max(max_consecutive, current_consecutive)
            else:
                current_consecutive = 0
                
        return max_consecutive 